import React, { useEffect, useRef } from 'react';
import type { CallGraphData } from './types';
import { Empty, Spin } from 'antd';
import G6 from '@antv/g6';

interface Props {
  data: CallGraphData | null;
  layout?: 'force' | 'dagre' | 'radial';
  loading?: boolean;
}

/**
 * 缩写包名或类名，将包路径的每一段缩写为首字母
 * 例如: com.htsc.ocrm.core.order.changecommission.wsd12dubbo -> c.h.o.c.o.c.wsd12dubbo
 * @param fullName 完整的包名或类名
 * @param maxLength 最大长度阈值，超过此长度才进行缩写
 * @returns 缩写后的名称
 */
const abbreviateName = (fullName: string, maxLength: number = 50): string => {
  if (!fullName || fullName.length <= maxLength) {
    return fullName;
  }

  const parts = fullName.split('.');
  if (parts.length <= 1) {
    return fullName;
  }

  // 保留最后一段完整，其他段缩写为首字母
  const abbreviated = parts.slice(0, -1).map(part => part.charAt(0)).join('.');
  return `${abbreviated}.${parts[parts.length - 1]}`;
};

const CallGraphViewer: React.FC<Props> = ({ data, layout = 'dagre', loading }) => {
  const ref = useRef<HTMLDivElement | null>(null);
  const graphRef = useRef<any>(null);

  useEffect(() => {
    if (!ref.current) return;
    const container = ref.current;
    const width = container.clientWidth || 800;
    const height = 600;

    // 注册自定义节点，支持多行文本
    G6.registerNode('multiline-node', {
      draw(cfg: any, group: any) {
        const { size = 32, style = {}, labelCfg = {} } = cfg;
        const r = size / 2;

        // 绘制圆形节点
        const shape = group.addShape('circle', {
          attrs: {
            x: 0,
            y: 0,
            r,
            ...style
          },
          name: 'circle-shape'
        });

        // 如果有 label，绘制多行文本
        if (cfg.label) {
          const lines = String(cfg.label).split('\n');
          const labelStyle = labelCfg.style || {};
          const fontSize = labelStyle.fontSize || 12;
          const lineHeight = fontSize * (labelStyle.lineHeight || 1.4);
          const startY = (labelCfg.offset || 10) + r;

          lines.forEach((line: string, index: number) => {
            group.addShape('text', {
              attrs: {
                x: 0,
                y: startY + index * lineHeight,
                text: line,
                fontSize,
                textAlign: 'center',
                textBaseline: 'top',
                fill: labelStyle.fill || '#262626',
                fontWeight: index === 0 ? (labelStyle.fontWeight || 400) : 400,
                ...labelStyle
              },
              name: `label-line-${index}`
            });
          });
        }

        return shape;
      },
      update: undefined
    }, 'circle');

    const tooltip = new G6.Tooltip({
      offsetX: 10,
      offsetY: 10,
      itemTypes: ['node'],
      getContent: (e: any) => {
        const m = e?.item?.getModel?.() || {};
        const props = m.properties || {};

        // 格式化时间
        const formatTime = (time: string) => {
          if (!time) return '';
          try {
            const date = new Date(time);
            return date.toLocaleString('zh-CN', {
              year: 'numeric',
              month: '2-digit',
              day: '2-digit',
              hour: '2-digit',
              minute: '2-digit'
            });
          } catch {
            return time;
          }
        };

        // 格式化数字
        const formatNumber = (num: number) => {
          if (num >= 10000) return `${(num / 10000).toFixed(1)}万`;
          if (num >= 1000) return `${(num / 1000).toFixed(1)}k`;
          return num.toString();
        };

        const sections = [];

        // 标题部分 - 优化显示
        const title = m.label || m.methodFullName || '未知';
        const typeIcon = m.domainType === 'TABLE' ? '📊' : '⚙️';

        // 提取方法名（去掉包名和参数）
        let displayTitle = title;
        if (m.methodFullName && m.methodFullName.includes(':')) {
          const parts = m.methodFullName.split(':');
          if (parts.length > 1) {
            const methodPart = parts[1];
            // 提取方法名（去掉参数）
            displayTitle = methodPart.split('(')[0];
          }
        }

        sections.push(`
          <div style="border-bottom: 2px solid #f0f0f0; padding-bottom: 10px; margin-bottom: 10px;">
            <div style="display: flex; align-items: center; gap: 6px; margin-bottom: 6px;">
              <span style="font-size: 16px;">${typeIcon}</span>
              <span style="font-size: 14px; font-weight: 600; color: #262626;">${displayTitle}</span>
            </div>
            ${m.domainType ? `<div style="display: inline-block; padding: 2px 8px; background: #f0f0f0; border-radius: 10px; font-size: 10px; color: #8c8c8c; font-weight: 500;">${m.domainType}</div>` : ''}
          </div>
        `);

        // 基本信息 - 优化布局
        if (m.methodFullName || m.className || m.packageName || (m.domainType === 'TABLE' && m.tableName)) {
          const basicInfo = [];

          // 完整方法签名已移除，点击节点查看详情

          // 包名 - 突出显示（超长时缩写）
          if (m.packageName) {
            const displayPackageName = abbreviateName(m.packageName, 50);
            const isAbbreviated = displayPackageName !== m.packageName;
            basicInfo.push(`
              <div style="background: #f0f5ff; padding: 6px 8px; border-radius: 4px; margin-bottom: 6px; border-left: 3px solid #597ef7;">
                <div style="display: flex; align-items: center; gap: 6px;">
                  <span style="color: #597ef7; font-size: 10px; font-weight: 600;">📦 PACKAGE</span>
                  ${isAbbreviated ? '<span style="color: #8c8c8c; font-size: 9px;">(缩写)</span>' : ''}
                </div>
                <div style="color: #262626; font-size: 11px; margin-top: 3px; font-family: 'Consolas', 'Monaco', monospace; font-weight: 500;" title="${m.packageName}">${displayPackageName}</div>
              </div>
            `);
          }

          // 类名 - 总是显示（如果有，超长时缩写）
          if (m.className) {
            const displayClassName = abbreviateName(m.className, 50);
            const isAbbreviated = displayClassName !== m.className;
            basicInfo.push(`
              <div style="background: #fff7e6; padding: 6px 8px; border-radius: 4px; margin-bottom: 6px; border-left: 3px solid #ffa940;">
                <div style="display: flex; align-items: center; gap: 6px;">
                  <span style="color: #fa8c16; font-size: 10px; font-weight: 600;">🏛️ CLASS</span>
                  ${isAbbreviated ? '<span style="color: #8c8c8c; font-size: 9px;">(缩写)</span>' : ''}
                </div>
                <div style="color: #262626; font-size: 11px; margin-top: 3px; font-family: 'Consolas', 'Monaco', monospace; font-weight: 500;" title="${m.className}">${displayClassName}</div>
              </div>
            `);
          }

          // 表名（仅TABLE类型）
          if (m.domainType === 'TABLE' && m.tableName) {
            basicInfo.push(`
              <div style="background: #f9f0ff; padding: 6px 8px; border-radius: 4px; border-left: 3px solid #722ed1;">
                <div style="color: #8c8c8c; font-size: 10px; margin-bottom: 2px;">数据表</div>
                <div style="color: #262626; font-size: 12px; font-weight: 500;">${m.tableName}</div>
              </div>
            `);
          }

          if (basicInfo.length > 0) {
            sections.push(`<div style="margin-bottom: 10px;">${basicInfo.join('')}</div>`);
          }
        }

        // 服务信息 - 卡片式设计
        if (props.serviceType || props.serviceName) {
          const typeMap: Record<string, { label: string; color: string; bgColor: string; icon: string }> = {
            REST: { label: 'REST API', color: '#52c41a', bgColor: '#f6ffed', icon: '🌐' },
            DUBBO: { label: 'Dubbo', color: '#1890ff', bgColor: '#e6f7ff', icon: '🔌' },
            MAIN: { label: '入口', color: '#722ed1', bgColor: '#f9f0ff', icon: '▶️' },
            NONE: { label: '普通', color: '#8c8c8c', bgColor: '#fafafa', icon: '⚙️' }
          };
          const typeInfo = typeMap[props.serviceType] || { label: props.serviceType, color: '#8c8c8c', bgColor: '#fafafa', icon: '⚙️' };
          sections.push(`
            <div style="background: ${typeInfo.bgColor}; padding: 8px 10px; border-radius: 6px; margin-bottom: 10px; border-left: 3px solid ${typeInfo.color};">
              <div style="display: flex; align-items: center; gap: 6px; margin-bottom: ${props.serviceName ? '4px' : '0'};">
                <span style="font-size: 14px;">${typeInfo.icon}</span>
                <span style="color: ${typeInfo.color}; font-weight: 600; font-size: 12px;">${typeInfo.label}</span>
              </div>
              ${props.serviceName ? `<div style="color: #595959; font-size: 11px; margin-left: 20px; font-family: 'Consolas', 'Monaco', monospace;">${props.serviceName}</div>` : ''}
            </div>
          `);
        }

        // APM 调用统计 - 数据卡片
        if (props.apmExists && (props.apmCount6m != null || props.apmCount1y != null)) {
          const stats = [];
          if (props.apmCount1y != null) {
            stats.push(`
              <div style="flex: 1; min-width: 70px; background: white; padding: 6px 8px; border-radius: 4px; text-align: center;">
                <div style="color: #1890ff; font-weight: 700; font-size: 16px; line-height: 1.2;">${formatNumber(props.apmCount1y)}</div>
                <div style="color: #8c8c8c; font-size: 10px; margin-top: 2px;">年调用</div>
              </div>
            `);
          }
          if (props.apmCount6m != null) {
            stats.push(`
              <div style="flex: 1; min-width: 70px; background: white; padding: 6px 8px; border-radius: 4px; text-align: center;">
                <div style="color: #52c41a; font-weight: 700; font-size: 16px; line-height: 1.2;">${formatNumber(props.apmCount6m)}</div>
                <div style="color: #8c8c8c; font-size: 10px; margin-top: 2px;">半年</div>
              </div>
            `);
          }
          if (props.apmAvgDailyCalls != null) {
            stats.push(`
              <div style="flex: 1; min-width: 70px; background: white; padding: 6px 8px; border-radius: 4px; text-align: center;">
                <div style="color: #faad14; font-weight: 700; font-size: 16px; line-height: 1.2;">${props.apmAvgDailyCalls.toFixed(1)}</div>
                <div style="color: #8c8c8c; font-size: 10px; margin-top: 2px;">日均</div>
              </div>
            `);
          }
          sections.push(`
            <div style="background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%); padding: 8px 10px; border-radius: 6px; margin-bottom: 10px; border-left: 3px solid #1890ff;">
              <div style="color: #1890ff; font-weight: 600; font-size: 11px; margin-bottom: 6px; display: flex; align-items: center; gap: 4px;">
                <span style="font-size: 14px;">📊</span>
                <span>调用统计</span>
              </div>
              <div style="display: flex; gap: 6px;">${stats.join('')}</div>
            </div>
          `);
        }

        // 废弃分析 - 醒目警告卡片
        if (props.deprecationLevel) {
          const levelMap: Record<string, { label: string; emoji: string; color: string; bgColor: string; borderColor: string }> = {
            MINIMAL_RISK: { label: '活跃', emoji: '✅', color: '#52c41a', bgColor: '#f6ffed', borderColor: '#b7eb8f' },
            LOW_RISK: { label: '低风险', emoji: '🟢', color: '#52c41a', bgColor: '#f6ffed', borderColor: '#b7eb8f' },
            MEDIUM_RISK: { label: '中风险', emoji: '🟡', color: '#faad14', bgColor: '#fffbe6', borderColor: '#ffe58f' },
            HIGH_RISK: { label: '高风险', emoji: '🟠', color: '#fa8c16', bgColor: '#fff7e6', borderColor: '#ffd591' },
            EXTREME_RISK: { label: '极高风险', emoji: '🔴', color: '#f5222d', bgColor: '#fff1f0', borderColor: '#ffccc7' }
          };
          const levelInfo = levelMap[props.deprecationLevel] || { label: props.deprecationLevel, emoji: '⚠️', color: '#8c8c8c', bgColor: '#f5f5f5', borderColor: '#d9d9d9' };
          sections.push(`
            <div style="background: ${levelInfo.bgColor}; padding: 8px 10px; border-radius: 6px; margin-bottom: 10px; border: 2px solid ${levelInfo.borderColor};">
              <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px;">
                <div style="display: flex; align-items: center; gap: 6px;">
                  <span style="font-size: 16px;">${levelInfo.emoji}</span>
                  <span style="color: ${levelInfo.color}; font-weight: 700; font-size: 12px;">${levelInfo.label}</span>
                </div>
                ${props.deprecationScore != null ? `<div style="background: white; padding: 2px 8px; border-radius: 10px; color: ${levelInfo.color}; font-weight: 700; font-size: 11px;">${props.deprecationScore}</div>` : ''}
              </div>
              ${props.deprecationReason ? `<div style="color: #595959; font-size: 10px; line-height: 1.4; margin-top: 4px; padding: 4px 6px; background: white; border-radius: 3px;">${props.deprecationReason}</div>` : ''}
            </div>
          `);
        }

        // Git 信息 - 简洁卡片
        if (props.gitLastEditTime || props.gitLastEditEmp) {
          sections.push(`
            <div style="background: #fafafa; padding: 6px 8px; border-radius: 4px; margin-bottom: 8px; border-left: 2px solid #d9d9d9;">
              <div style="color: #595959; font-weight: 600; font-size: 10px; margin-bottom: 4px; display: flex; align-items: center; gap: 4px;">
                <span>📝</span>
                <span>Git</span>
              </div>
              <div style="display: flex; flex-direction: column; gap: 2px;">
                ${props.gitLastEditTime ? `<div style="color: #8c8c8c; font-size: 10px;">🕐 ${formatTime(props.gitLastEditTime)}</div>` : ''}
                ${props.gitLastEditEmp ? `<div style="color: #8c8c8c; font-size: 10px;">👤 ${props.gitLastEditEmp}</div>` : ''}
              </div>
            </div>
          `);
        }

        // SA 扫描 - 简洁卡片
        if (props.saExists && props.saLastInvocationTime) {
          sections.push(`
            <div style="background: #fafafa; padding: 6px 8px; border-radius: 4px; border-left: 2px solid #d9d9d9;">
              <div style="color: #595959; font-weight: 600; font-size: 10px; margin-bottom: 4px; display: flex; align-items: center; gap: 4px;">
                <span>🔍</span>
                <span>SA扫描</span>
              </div>
              <div style="color: #8c8c8c; font-size: 10px;">最后调用: ${formatTime(props.saLastInvocationTime)}</div>
            </div>
          `);
        }

        return `
          <div style="
            padding: 10px 12px; 
            font-size: 12px; 
            max-width: 380px; 
            background: white;
            border-radius: 6px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
            line-height: 1.5;
          ">
            ${sections.join('')}
          </div>
        `;
      }
    });

    const graph = new G6.Graph({
      container,
      width,
      height,
      minZoom: 0.3,
      maxZoom: 3,
      animate: true,
      layout:
        layout === 'force'
          ? { type: 'force', preventOverlap: true, nodeSize: 35, nodeSpacing: 50, linkDistance: 150 }
          : layout === 'dagre'
            ? { type: 'dagre', rankdir: 'TB', nodesep: 40, ranksep: 80, controlPoints: true }
            : { type: layout },
      modes: { default: ['drag-canvas', 'zoom-canvas', 'drag-node', 'click-select'] },
      defaultNode: {
        size: 32,
        style: {
          lineWidth: 2,
          stroke: '#666',
          fill: '#fff',
          shadowColor: 'rgba(0, 0, 0, 0.15)',
          shadowBlur: 10,
          shadowOffsetX: 2,
          shadowOffsetY: 2
        }
      },
      defaultEdge: {
        type: 'polyline',
        style: {
          endArrow: {
            path: 'M 0,0 L 8,4 L 8,-4 Z',
            fill: '#8c8c8c'
          },
          stroke: '#bfbfbf',
          lineWidth: 1.5
        }
      },
      plugins: [tooltip]
    });

    // 渲染/布局完成后，自动适配并居中
    graph.on('afterlayout', () => {
      try {
        graph.fitView(50, { onlyOutOfViewPort: false, direction: 'both' }, true);
        graph.fitCenter();
        normalizeZoom(graph);
      } catch { /* ignore */ }
    });

    graph.on('afterrender', () => {
      try {
        graph.fitView(50, { onlyOutOfViewPort: false, direction: 'both' }, true);
        graph.fitCenter();
        normalizeZoom(graph);
      } catch { /* ignore */ }
    });

    // 节点点击事件已禁用

    // Tooltip 已通过插件处理

    graphRef.current = graph;
    return () => graph?.destroy?.();
  }, [layout]);

  // 窗口尺寸变化时，自适应并居中
  useEffect(() => {
    const onResize = () => {
      if (!ref.current || !graphRef.current) return;
      const w = ref.current.clientWidth || 800;
      const h = 600;
      try {
        const graph = graphRef.current;
        graph.changeSize(w, h);
        graph.fitView(40, { onlyOutOfViewPort: false, direction: 'both' }, true);
        graph.fitCenter();
      } catch { /* ignore */ }
    };
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
  }, []);

  useEffect(() => {
    if (!graphRef.current || !data) return;

    const nodes = (data.nodes || []).map((n) => {
      // 根据节点类型设置不同的大小
      let nodeSize = 32;  // 默认大小
      const isEntry = n.category === 'ENTRY' || (n as any).isEntry || n.properties?.isEntry || n.properties?.serviceType === 'MAIN';
      const isTable = n.type === 'TABLE' || n.type === 'Table';

      if (isEntry) {
        nodeSize = 40;  // 入口方法更大
      } else if (isTable) {
        nodeSize = 36;  // 表节点稍大
      }

      // 构建节点标签：两行显示（类名 + 方法名）
      let displayLabel = n.label ?? '';

      if (n.type === 'METHOD' || n.type === 'Method') {
        if (n.className && n.label) {
          // 提取简短类名（去掉包名）
          const simpleClassName = n.className.split('.').pop() || n.className;
          // 提取方法名（去掉参数）
          const methodName = n.label.includes('(') ? n.label.split('(')[0] : n.label;
          // 使用换行符分隔类名和方法名
          displayLabel = `${simpleClassName}\n${methodName}`;
        }
      }

      return {
        id: n.id,
        type: 'multiline-node',  // 使用自定义节点类型
        label: displayLabel,
        size: nodeSize,
        // 业务字段直接挂到 model，tooltip 中可读取
        domainType: n.type,
        category: n.category,
        isEntry: (n as any).isEntry || n.properties?.isEntry,
        methodFullName: n.methodFullName,
        className: n.className,
        packageName: n.packageName,
        tableName: n.tableName,
        properties: n.properties || {},  // 传递所有扩展属性
        style: getNodeStyle(n.type, n.category, n.properties),
        labelCfg: {
          offset: 10,
          style: {
            fill: '#262626',
            fontSize: isEntry ? 12 : 11,
            fontWeight: isEntry ? 600 : 400,
            lineHeight: 1.4
          }
        }
      };
    });

    const edges = (data.edges || []).map((e) => ({
      id: e.id,
      source: e.source,
      target: e.target,
      label: e.label ?? '',
      labelCfg: {
        autoRotate: true,
        refY: -6,
        style: {
          fontSize: 11,
          fill: '#8c8c8c',
          background: {
            fill: 'rgba(255, 255, 255, 0.9)',
            padding: [2, 4, 2, 4],
            radius: 2
          }
        }
      },
      style: {
        endArrow: {
          path: 'M 0,0 L 8,4 L 8,-4 Z',
          fill: '#bfbfbf'
        },
        stroke: '#d9d9d9',
        lineWidth: 1.5,
        lineAppendWidth: 10
      }
    }));

    const graph = graphRef.current;
    graph.data({ nodes, edges });
    graph.render();
    // 触发一次布局，force 会自动迭代展开
    try { graph.layout(); } catch { /* ignore if not needed */ }

    // 计算“核心节点”：优先 isEntry，其次度数最高
    const degree: Record<string, number> = {};
    edges.forEach((e: any) => {
      degree[e.source] = (degree[e.source] || 0) + 1;
      degree[e.target] = (degree[e.target] || 0) + 1;
    });
    const entry = nodes.find((n: any) => n.isEntry);
    let centerId = entry?.id as string | undefined;
    if (!centerId) {
      let max = -1; let maxId: string | undefined;
      nodes.forEach((n: any) => { const d = degree[n.id] || 0; if (d > max) { max = d; maxId = n.id; } });
      centerId = maxId || nodes[0]?.id;
    }

    // 先立即居中整个图
    setTimeout(() => {
      try {
        graph.fitView(50, { onlyOutOfViewPort: false, direction: 'both' }, true);
        graph.fitCenter();
        normalizeZoom(graph);
      } catch { /* ignore */ }
    }, 150);

    // 然后聚焦到核心节点（可选，如果不需要聚焦可以注释掉）
    if (centerId) {
      setTimeout(() => {
        try {
          graph.focusItem(centerId!, false);  // 不使用动画，避免偏移
          graph.fitCenter();  // 再次居中
        } catch { /* ignore */ }
      }, 400);
    }
  }, [data]);

  const getNodeStyle = (type?: string, category?: string, properties?: any) => {
    const baseStyle = {
      shadowColor: 'rgba(0, 0, 0, 0.2)',
      shadowBlur: 12,
      shadowOffsetX: 3,
      shadowOffsetY: 3,
      lineWidth: 2.5,
      cursor: 'pointer'
    };

    // 表节点 - 黄色（支持 TABLE 和 Table 两种格式）
    if (type === 'TABLE' || type === 'Table') {
      return {
        ...baseStyle,
        fill: 'l(0) 0:#fff7e6 1:#fffbe6',  // 渐变黄色
        stroke: '#faad14',
        lineWidth: 3,
        shadowColor: 'rgba(250, 173, 20, 0.3)'
      };
    }

    // 入口方法 - 紫色（优先级高于废弃等级）
    if (category === 'ENTRY' || properties?.isEntry === true || properties?.serviceType === 'MAIN') {
      return {
        ...baseStyle,
        fill: 'l(0) 0:#f0e6ff 1:#f9f0ff',  // 渐变紫色
        stroke: '#722ed1',
        lineWidth: 3,
        shadowColor: 'rgba(114, 46, 209, 0.3)'
      };
    }

    // 根据废弃等级着色
    if (properties?.deprecationLevel) {
      const level = properties.deprecationLevel;
      if (level === 'EXTREME_RISK') {
        return {
          ...baseStyle,
          fill: 'l(0) 0:#ffe6e6 1:#fff1f0',  // 渐变深红
          stroke: '#cf1322',
          lineWidth: 3,
          shadowColor: 'rgba(207, 19, 34, 0.3)'
        };
      }
      if (level === 'HIGH_RISK') {
        return {
          ...baseStyle,
          fill: 'l(0) 0:#ffe9e0 1:#fff2e8',  // 渐变橙红
          stroke: '#fa541c',
          shadowColor: 'rgba(250, 84, 28, 0.3)'
        };
      }
      if (level === 'MEDIUM_RISK') {
        return {
          ...baseStyle,
          fill: 'l(0) 0:#ffedd6 1:#fff7e6',  // 渐变橙色
          stroke: '#fa8c16',
          shadowColor: 'rgba(250, 140, 22, 0.3)'
        };
      }
      if (level === 'LOW_RISK') {
        return {
          ...baseStyle,
          fill: 'l(0) 0:#fff5d6 1:#fffbe6',  // 渐变黄色
          stroke: '#faad14',
          shadowColor: 'rgba(250, 173, 20, 0.3)'
        };
      }
      if (level === 'MINIMAL_RISK') {
        return {
          ...baseStyle,
          fill: 'l(0) 0:#e6f9ed 1:#f6ffed',  // 渐变绿色
          stroke: '#52c41a',
          shadowColor: 'rgba(82, 196, 26, 0.3)'
        };
      }
    }

    // 默认蓝色
    return {
      ...baseStyle,
      fill: 'l(0) 0:#d6e9ff 1:#e6f7ff',  // 渐变蓝色
      stroke: '#1890ff',
      shadowColor: 'rgba(24, 144, 255, 0.3)'
    };
  };

  if (loading) {
    return (
      <div style={{ textAlign: 'center', padding: '100px 0' }}>
        <Spin size="large" />
        <div style={{ marginTop: 16 }}>正在加载调用关系图...</div>
      </div>
    );
  }

  if (!data) {
    return <Empty description="请输入搜索条件查询调用关系图" image={Empty.PRESENTED_IMAGE_SIMPLE} />;
  }

  const handleZoom = (type: 'in' | 'out' | 'fit') => {
    const graph = graphRef.current;
    if (!graph) return;
    const min = graph.get('minZoom') || 0.3;
    const max = graph.get('maxZoom') || 3;
    if (type === 'fit') {
      graph.fitView(20, { onlyOutOfViewPort: false, direction: 'both' }, true);
      return;
    }
    const step = 0.2;
    const current = graph.getZoom();
    const target = type === 'in' ? Math.min(current + step, max) : Math.max(current - step, min);
    graph.zoomTo(target);
  };

  return (
    <div style={{ position: 'relative', width: '100%', height: 600 }}>
      <div ref={ref} style={{ width: '100%', height: '100%' }} />
      {/* Zoom toolbar */}
      <div style={{ position: 'absolute', right: 12, top: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
        <button onClick={() => handleZoom('in')} style={{ width: 32, height: 32, border: '1px solid #d9d9d9', background: '#fff', borderRadius: 4, cursor: 'pointer' }}>+</button>
        <button onClick={() => handleZoom('out')} style={{ width: 32, height: 32, border: '1px solid #d9d9d9', background: '#fff', borderRadius: 4, cursor: 'pointer' }}>-</button>
        <button onClick={() => handleZoom('fit')} style={{ width: 32, height: 32, border: '1px solid #d9d9d9', background: '#fff', borderRadius: 4, cursor: 'pointer' }}>⤢</button>
      </div>
    </div>
  );
};

export default CallGraphViewer;
const normalizeZoom = (graph: any) => {
  try {
    const minZ = graph.get('minZoom') || 0.3;
    const maxZ = graph.get('maxZoom') || 3;
    const z = graph.getZoom();

    // 首先确保缩放比例在允许范围内
    if (z < minZ || z > maxZ) {
      const clampedZoom = Math.max(minZ, Math.min(maxZ, z));
      graph.zoomTo(clampedZoom, { x: graph.getWidth() / 2, y: graph.getHeight() / 2 });
      return;
    }

    // 希望初始不要过大或过小，限制在 [0.6, 1.0]
    let target = z;
    const preferredMin = 0.6;
    const preferredMax = 1.0;
    if (z > preferredMax) target = Math.min(preferredMax, maxZ);
    if (z < preferredMin) target = Math.max(preferredMin, minZ);
    if (Math.abs(target - z) > 0.01) graph.zoomTo(target);
  } catch { /* ignore */ }
};
